import {
	unstable_ImmediatePriority as ImmediatePriority,
	unstable_UserBlockingPriority as UserBlockingPriority,
	unstable_NormalPriority as NormalPriority,
	unstable_LowPriority as LowPriority,
	unstable_IdlePriority as IdlePriority,
	unstable_scheduleCallback as scheduleCallback,
	unstable_shouldYield as shouldYield,
	CallbackNode,
	unstable_getFirstCallbackNode as getFirstCallbackNode,
	unstable_cancelCallback as cancelCallback
} from 'scheduler';

import './style.css';
const button = document.querySelector('button');
const root = document.querySelector('#root');

type Priority =
	| typeof IdlePriority
	| typeof LowPriority
	| typeof NormalPriority
	| typeof UserBlockingPriority
	| typeof ImmediatePriority;

interface Work {
	count: number;
	priority: Priority;
}

const workList: Work[] = [];
let prevPriority: Priority = IdlePriority; // 之前的任务的优先级
let task: CallbackNode | null = null; // 当前调度的回调函数 被调度器包装返回的任务
// task.callBack 表示使用scheduleCallback时传入的函数 开始执行这个函数的时候 会被置为null 当该函数是因为 中断执行的时候task.callBack 会被置为 callBack函数执行返回的函数
// 如果是正常执行完的 且没有插入更高优先级任务到 调度器的最小堆队列 task.callBack就会被置为null

[LowPriority, NormalPriority, UserBlockingPriority, ImmediatePriority].forEach(
	(priority) => {
		const btn = document.createElement('button');
		root?.appendChild(btn);
		btn.innerText = [
			'',
			'ImmediatePriority', // 同步任务 是放入宏任务中执行的  不会 中断的 会阻塞浏览器渲染的
			'UserBlockingPriority',
			'NormalPriority',
			'LowPriority'
		][priority];
		btn.onclick = () => {
			// 每点击一次 就像workList 插入一次  按钮对应的任务等级的更新任务
			workList.unshift({
				count: 100, //count 代表着 就是  一个任务里面 有多少个组件需要处理 也可以理解 记录到 处理到了 第几个组件 对应 fiber链表
				priority: priority as Priority
			});
			schedule(); // 开启调度 从 workList 中取出任务 并调度
		};
	}
);

function schedule() {
	// 该函数 就是取出最小堆中 堆顶顶任务 也就是 优先级最高的 任务
	const cbNode = getFirstCallbackNode(); // 获取当前正在调度任务
	// 排序 优先级高的在前面
	// 取优先级最高的的任务
	// 这个 workList 就相当于react 里面的 root.pendingLanes 优先级lane
	const curWork = workList.sort((w1, w2) => w1.priority - w2.priority)[0];
	// 注意这里没有从 数组里面 删除任务 只是取出第一个 后面任务被中断 了 还是取出 优先级最高的任务
	// 只有当任务执行完了 才会从数组中删除

	// 策略逻辑
	if (!curWork) {
		//curWork 不存在了  说明数组空了
		task = null; // 重置
		cbNode && cancelCallback(cbNode); // 取消之前的调度
		return;
	}

	const { priority: curPriority } = curWork;
	// curPriority 当前任务的优先级
	// 如果上一个任务是正常执行完的 prevPriority会被重置
	if (curPriority === prevPriority) {
		// 如果当前任务优先级 和 上一次任务优先级一样
		// 有两种可能
		// 1:之前任务被中断执行的：中断执行的 prevPriority不会被重置 然后重新执行 schedule 此时curPriority === prevPriority相等 直接 返回 然后task 也不会被重置  那么 prevTask 和 newTask 就相等  然后 perform 就会返回一个函数 然后调度器就会将该函数作为当前任务的回调函数继续调度

		// 2:用户又拆入了同一个优先级的任务 也会执行schedule 且当前优先级还是最高的 因为curWork 取出来 优先级永远是最高的 也直接return 当当前正在执行的任务执行完后 会继续执行 schedule 从workList里面取 出新插入的任务执行。
		// 就直接return;
		return;
	}

	// 因为有些任务是 宏任务中执行的 所以任务执行过程中 伴随着切片 主线程可以去 渲染 也可以去从 微任务队列 和交互队列取任务执行
	//    所以当用户点击的时候 点击的回调是插入 交互队列的 他比宏任务的优先级更高 所以可以响应点击 。点击产生了一个更高级的任务 此时开始调度
	// 发现之前 有任务再调度 但这个优先级是更高的 所以取消之前的调度 将该任务开始调度。
	// 取消调度就是从 调度器最小堆中删除该任务的 回调。删除了那怎么补上？？ 因为workList中并没有删除该任务 后面插入的任务执行完了 会继续执行schedule 调度就会将该删除的任务重新插入最小堆中

	// 这里表示 在 一个任务正在执行的过程中 用户又点击或者设置的插入了一个更高优先级的任务 并且开始了调度 因为 取的 work始终是优先级最高的
	// 为什么能确定当前的 work 是最高优先级的呢？ 因为他是在 workList 优先级是最高的 为什么能确定 比 当前正在执行的任务优先级更高呢
	// 因为 当前正在执行的 work 还没从workList 中删除 所以能走到这 说明插入了一个优先级更高的work
	// 所以要取消掉之前的任务调度 重新调度这个优先级高的任务
	// 更高优先级的work

	// 这里就 做到了 一个 任务在不断的中断执行中断执行 然后浏览器可以响应点击 插入了一个更高优先级的任务 然后 直接中断当前 任务的执行 去重新调度！！！！！！
	cbNode && cancelCallback(cbNode);
	// 为什么

	// 点击 ImmediatePriority 按钮 先输出 微任务执行了1 微任务执行了2 再输出100次 同步任务执行了 再  输出 宏任务执行了2
	// 证明ImmediatePriority 同步任务 也是 放入 宏任务队列中调度的！！！！
	setTimeout(() => {
		console.log('宏任务执行了1', 'log');
	});
	Promise.resolve().then(() => {
		console.log('微任务执行了1', 'log');
	});

	// 调取这个任务 根据任务的优先级判断 如果是 为1 的 不可中断

	// 不断的往 schedule调度器里面的  最小堆添加 任务 并返回 当前添加的回调函数 经过包装的任务

	task = scheduleCallback(curPriority, perform.bind(null, curWork));
	// curCallback有值表示当前任务没执行完 被中断了 然后perform返回了一个函数 scheduleCallback 会在浏览器渲染之后 重新再去调度这个函数
	setTimeout(() => {
		console.log('宏任务执行了2', 'log');
	});
	Promise.resolve().then(() => {
		console.log('微任务执行了2', 'log');
	});
}

function perform(work: Work, didTimeout?: boolean) {
	// 可以将 perform 想象成一次 render 和commit过程

	// 什么情况下中断 什么情况不中断 根据下面三个条件来判断
	/**
	 * 1. work.priority 如果任务本身优先级 就是一个 同步的 那么就不能中断
	 * 2. 饥饿问题 如果一个任务 一直得不到执行 优先级竞争不过别的任务 给任务设置过期时间 如果过期了 他的优先级就变成 同步任务  同步任务优先级是最高的
	 * 3. 时间切片  shouldYield 表示 需要切片了 就是没时间 了
	 */
	const needSync = work.priority === ImmediatePriority || didTimeout;
	while ((needSync || !shouldYield()) && work.count) {
		// 继续执行的 条件 任务没执行完 且 (是一个同步任务 或者 时间切片没到)
		// 如果 没时间了 但任务是 同步的 那么也需要继续执行
		work.count--;
		console.log('同步任务执行了', 'log');

		insertSpan(work.priority + '');
	}

	// 中断执行 或者 执行完  了 就记录下 这个任务的 优先级 记录成 上一个任务的优先级
	prevPriority = work.priority;

	if (!work.count) {
		// 任务执行完了
		const workIndex = workList.indexOf(work);
		workList.splice(workIndex, 1); // 如果是 顺利执行完了的话 就将该任务从 workList 中 删除
		// 如果是 执行完了 就重置之前的任务的优先级
		prevPriority = IdlePriority; //因为是顺利执行完了的所有 重置prevPriority
	}

	// 走到这里表示任务可能
	// 可能1:中断执行的 那prevPriority 没变还是当前任务的 优先级 那么重新调度之后 会直接return 不会往 最小堆中插入任务 那prevTask 和 newTask 就是同一个任务 那就需要返回一个函数
	// 让调度器继续调度这个任务
	// 可能2:顺利执行完了 此时prevPriority已经被重置了 且 当前work也从workList中删除了 所以 prevPriority 和  curPriority 可能就不想当了 就会将新任务插入到 调度器的最小堆中
	// 此时 task 就是一个新的任务 。perform 就会返回null 然后调度器 得知perform 返回了 null 就会将本次任务从最小堆中删除 然后等浏览器空闲是从最小堆中取优先级最高任务执行@！！！！
	const prevTask = task; // 当前任务
	schedule(); // 重新调度
	const newTask = task; // 新任务

	if (newTask && prevTask === newTask) {
		// scheduleCallback 调度器有优化 如果 传进去的函数 返回了一个函数 那会继续以当前优先级 调度这个返回的函数
		// 传进去的函数 返回了一个函数 则表示 当前回调函数 是 被中断执行的 没有执行完
		// 然后 调度器会将 该函数重新赋值 给scheduleCallback  返回的 任务的 callBack
		return perform.bind(null, work); // bind了 最新的 work 返回一个函数 接受 调度器执行的 didTimeout参数
	}
}

function insertSpan(content) {
	const span = document.createElement('span');
	span.innerText = content;
	span.className = `pri-${content}`;
	doSomeBuzyWork(10000000);
	// 宏任务 是一个一个数字出现的 执行一下 渲染一下
	// ImmediatePriority 宏任务 同步任务是一起出现的 因为他执行这个插入过程是 会阻塞浏览器渲染的 主线程要执行完这个宏任务 再去渲染 所以是一起出现的
	root?.appendChild(span); // 渲染这里插入了 dom 但如果是不可中断执行的 必须等一次性全部插完 然后线程还给 浏览器渲染 才会看到效果
}

function doSomeBuzyWork(len: number) {
	// 人为降低速度
	let result = 0;
	while (len--) {
		result += len;
	}
}
